7.2 执行机制
接口使用一个名为itab的结构存储运行期所需的相关类型信息。
type iface struct{
tab *itab // 类型信息
data unsafe.Pointer // 实际对象指针
}
type itab struct{
inter *interfacetype // 接口类型
_type *_type // 实际对象类型
fun [1]uintptr // 实际对象方法地址
}利用调试器,我们可查看这些结构存储的具体内容。
type Ner interface{
a()
b(int)
c(string)string
}
type N int
func(N) a() {}
func(*N)b(int) {}
func(*N)c(string)string{return"" }
func main() {
var n N
var t Ner= &n
t.a()
}输出:
$go build-gcflags"-N-l"
$gdb test
...
(gdb)info locals # 设置断点,运行,查看局部变量信息
&n=0xc82000a130
t= {
tab=0x12f028,
data=0xc82000a130
}
(gdb)p*t.tab.inter.typ._string # 接口类型名称
$17=0x737f0"main.Ner"
(gdb)p*t.tab._type._string # 实际对象类型
$20=0x707a0"*main.N"
(gdb)p t.tab.inter.mhdr # 接口类型方法集
$27= {
array=0x60158<type.*+72888>,
len=3,
cap=3
}
(gdb)p*t.tab.inter.mhdr.array[0].name # 接口方法名称
$30=0x70a48"a"
(gdb)p*t.tab.inter.mhdr.array[1].name
$31=0x70b08"b"
(gdb)p*t.tab.inter.mhdr.array[2].name
$32=0x70ba0"c"
(gdb)info symbol t.tab.fun[0] # 实际对象方法地址
main.(*N).a in section.text
(gdb)info symbol t.tab.fun[1]
main.(*N).b in section.text
(gdb)info symbol t.tab.fun[2]
main.(*N).c in section.text
很显然,相关类型信息里保存了接口和实际对象的元数据。同时,itab还用fun数组(不定长结构)保存了实际方法地址,从而实现在运行期对目标方法的动态调用。
除此之外,接口还有一个重要特征:将对象赋值给接口变量时,会复制该对象。
type data struct{
x int
}
func main() {
d:=data{100}
var t interface{} =d
println(t.(data).x)
}输出:
$go build-gcflags"-N-l"
$gdb test
(gdb)info locals # 输出局部变量
d= {
x=100
}
t= {
_type=0x5ec00<type.*+67296>,
data=0xc820035f20 # 接口变量存储的对象地址
}
(gdb)p/x&d # 局部变量地址。显然和接口存储的不是同一对象
$1=0xc820035f10
我们甚至无法修改接口存储的复制品,因为它也是unaddressable的。
func main() {
d:=data{100}
var t interface{} =d
p:= &t.(data) // 错误:cannot take the address of t.(data)
t.(data).x=200 // 错误:cannot assign to t.(data).x
}即便将其复制出来,用本地变量修改后,依然无法对iface.data赋值。解决方法就是将对象指针赋值给接口,那么接口内存储的就是指针的复制品。
func main() {
d:=data{100}
var t interface{} = &d
t.(*data).x=200
println(t.(*data).x)
}
输出:
$go build-gcflags"-N-l" && ./test
200
$gdb test
(gdb)info locals # 显示局部变量
d= {
x=100
}
t= {
_type=0x50480<type.*+8096>,
data=0xc820035f10
}
(gdb)p/x&d # 显然和接口内data存储的地址一致
$1=0xc820035f10
只有当接口变量内部的两个指针(itab,data)都为nil时,接口才等于nil。
func main() {
var a interface{} =nil
var b interface{} = (*int)(nil)
println(a==nil,b==nil)
}输出:
true false
(gdb)info locals
b= {
_type=0x500c0<type.*+7616>, # 显然b包含了类型信息
data=0x0
}
a= {
_type=0x0,
data=0x0
}
由此造成的错误并不罕见,尤其是在函数返回error时。
type TestError struct{}
func(*TestError)Error()string{
return"error"
}
func test(x int) (int,error) {
var err*TestError
if x<0{
err=new(TestError)
x=0
}else{
x+=100
}
return x,err // 注意: 这个err是有类型的
}
func main() {
x,err:=test(100)
if err!=nil{
log.Fatalln("err!=nil") // 此处被执行
}
println(x)
}输出:
2020/01/01 19:48:27 err!=nil
exit status 1
(gdb)info locals # 很显然x没问题,但err并不等于nil
x=200
err= {
tab=0x2161e8, #tab!=nil
data=0x0
}
正确做法是明确返回nil。
func test(x int) (int,error) {
if x<0{
return 0,new(TestError)
}
return x+100,nil
}